/*
------------------------------
Copyright ? 2010 Henry Golding
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
------------------------------
*/
#ifndef _SimpleSAPIIntegration_h__
#define _SimpleSAPIIntegration_h__

/**
---------------------------
Simple SAPI 5.3 integration
---------------------------
This is a simple integration of basic Text to Speech (TTS) and
Speech Recognition (SR) features provided by the Microsoft Speech
API 5.3. The SAPI itself if a very good and full featured API, but
a little arcane for first time users. This code is intended to be
an easy way to get basic functionality without much effort.

It provides a basic interface for TTS, but TTS is quite easy to
integrate anyway by following the tutorial at
http://msdn.microsoft.com/en-us/library/ms720163%28VS.85%29.aspx

However, there is no tutorial for the SR features, so I hope this
code will be of use to those trying to get basic speech recognition
working. This code provides a simple dynamic 'command and control'
grammar (as opposed to the 'dictation' grammar which is easier to
get working but fairly useless for games). You can specify a word
or phrase at runtime and this code will create a grammar rule to
listen for it. If you require more advanced features like optional
words then you may want to look into writing your own XML grammar.

----------
How to use
----------
Call Init() once before first use.

Call Update() once per frame.

Call Speak() to use TTS. Hopefully the parameters are self-explanatory.

To use SR you must supply a callback through SetCallback(). When you call
Update() the SR queue is polled and any successful recognitions will be
passed to the callback.

Use AddCommand() to add words and phrases to listen for.
*/

#pragma comment(lib, "sapi.lib")

#include <string>
#include <vector>

/** A singleton providing the SAPI functionality */
class SimpleSAPI
{
private:
	SimpleSAPI();
	SimpleSAPI(const SimpleSAPI&); // Not implemented
	SimpleSAPI& operator=(const SimpleSAPI&); // Not implemented
	~SimpleSAPI();

public:

	enum LanguageID
	{
		Lang_EnglishUS = 409,
		Lang_EnglishUK = 809
	};

	/** Retrieve the single instance of this class */
	static SimpleSAPI& GetInstance();

	/** Initialize COM and set up SAPI for TTS and SR */
	void Init(LanguageID srLanguage = Lang_EnglishUK);

	// SR

	/** Add a new command to the SR grammar
	@param command A word or phrase to be recognized
	@return The id assigned to the new rule, or -1 if operation failed
	*/
	int AddCommand(const std::wstring& command);

	/** Update - checks to see if any speech has been recognized */
	void Update();

	/** A callback used to inform the client when a word or
	phrase has been recognized. The first parameter is the ID of the
	recognized phrase (returned when it was added) and the second is
	the recognized phrase itself.
	*/
	typedef void(*SimpleSAPICallback)(unsigned, const std::wstring&);

	/** Set the function to call when speech is recognized */
	void SetCallback(SimpleSAPICallback pCallback) { m_pCallback = pCallback; }

	// TTS

	/** Speak a test phrase for every voice available in the system */
	void TestVoices();

	/** Set the voice to the one specified (must exist) */
	void SetVoice(unsigned idx);

	// Speak the specified text with the current voice (set in Control Panel or though SetVoice()).
	// Optionally, consider XML tags
	// (see http://www.tapiex.com/ES_Help/WP_XML_TTS_Tutorial.htm)
	void Speak(const wchar_t* pText, bool bUseXML = false, bool bAsync = true, bool bPurgeQueue = false);
	void Speak(const std::wstring& text, bool bUseXML = false, bool bAsync = true, bool bPurgeQueue = false);
	void Speak(const std::string& text, bool bUseXML = false, bool bAsync = true, bool bPurgeQueue = false);

private:

	/** Uninitialize COM and destroy the SAPI voice */
	void Shutdown();

	void OnRecognized(const std::wstring& text);

	SimpleSAPICallback		m_pCallback;

	typedef std::vector< std::pair<unsigned, std::wstring> > SRList;
	SRList m_SpeechRules;
};

#endif // _SimpleSAPIIntegration_h__
